# -*- coding: utf-8 -*-
from __future__ import annotations
import re
import copy
import statistics
from dataclasses import dataclass
from enum import IntEnum
from typing import List, Tuple, Dict
# import sys  # 导入sys模块
# sys.setrecursionlimit(3000)  # 将默认的递归深度修改为3000


from pandas import Series

from core import logger
from core.enums import Period, DateFormat, EventType
from core.tactic import Tactic
from functions import *


@dataclass
class ChanTacticSelectionOption:
    trend: str
    deviateStatus: int
    """
    背驰状态 0: 非背驰 1：背驰形成中 2：背驰已形成（现在处于背驰后第一个相反走势）
    """
    deviateDegree: float
    """
    背驰度 0~1之间，越大背驰越大
    """
    sucessEventType: EventType
    failEventType: EventType
    nextPeriod: Period

    def compute(self, trend: str, deviateStatus: int, deviateDegree: float) -> bool:
        return self.trend == trend and self.deviateStatus == deviateStatus and deviateDegree > self.deviateDegree


class Amplitude:
    def __init__(self, kh: float, kl: float, mh: float, ml: float):
        self.kh: float = kh
        self.kl: float = kl
        self.mh: float = mh
        self.ml: float = ml

    def __str__(self):
        return "kh: " + str(self.kh) + "; kl:" + str(self.kl) + "; mh: " + str(self.mh) + "; ml:" + str(self.ml)


class Boundary:
    def __init__(self, start: str, end: str):
        self.start: str = start
        self.end: str = end

    def __str__(self):
        return "start: " + self.start + "; end: " + self.end


class TrendStatus:
    def __init__(self, trend: str, parentTrend: List[str], value: float, breaked: bool=False, pre: TrendStatus = None):
        self.trend: str = trend
        self.parentTrend: List[str] = parentTrend
        self.value: float = value
        self.breaked: bool = breaked
        """
        是否突破，突破趋势幅度或突破盘整区间
        """
        self.pre: TrendStatus = pre

    def isDeviate(self):
        """
        是否背驰
        :return:
        """
        return 0 < self.value < 1

    def isTrend(self):
        """
        是否是趋势
        :return:
        """
        if self.parentTrend is not None and len(self.parentTrend) > 0 and ("-" in self.parentTrend or "+" in self.parentTrend):
            return True
        return False

    def isTrendItem(self):
        """
        是否是趋势的项
        :return:
        """
        if self.parentTrend is not None and len(self.parentTrend) > 0 and self.parentTrend[0] in ["+", "-"]:
            return True
        return False

    def isConsolidationItem(self):
        """
        是否是盘整的项
        :return:
        """
        flag = False
        if self.parentTrend is not None and len(self.parentTrend) > 0 and self.parentTrend[0] in ["=", "#"]:
            flag = True
        return flag

    def setPre(self, pre: TrendStatus):
        """
        设置前置趋势状态
        :return:
        """
        self.pre = pre
        return self

    def hasPre(self):
        return self.pre is not None

    def __str__(self):
        s = "===>>["
        if self.isTrendItem:
            s = s + f"\t趋势[{self.trend}]"
        else:
            s = s + f"\t{self.trend}"

        if self.isDeviate():
            s = s + f"\t背驰 [{str(1 - self.value)}]"
        elif self.value > 0:
            s = s + f"\t接续[{str(self.value - 1)}]"

        if self.breaked:
            s = s + "\t突破"
        else:
            s = s + "\t调整"
        s = s + "]"
        if self.hasPre():
            s = s + f"\t\t前置：{str(self.pre)}"
        return s


class Trend:
    def __init__(self):
        self.trend: str = None
        """
        趋势类型： 
            +：上涨\n 
            ++：合并上涨（两个上涨之间下跌太少故合并)\n 
            #：上涨盘整\n 
            -：下跌 \n 
            --：合并下跌(两个下跌之间上涨太少故合并)\n 
            =：下跌盘整\n 
        """
        self.boundary: Boundary = None
        self.area: float = None
        self.count: int = None
        self.items: List[Trend] = []
        self.amplitude: Amplitude = None
        self.parent: Trend = None

    def lastItem(self):
        def lastest(trend: Trend):
            if trend.isTrend():
                return lastest(trend.items[-1])
            elif trend.trend in ["#", "="]:
                return trend.items[0]
            else:
                return trend
        return lastest(self)

    def firstItem(self):
        def firstest(trend: Trend):
            if trend.isTrend():
                return firstest(trend.items[-1])
            elif trend.trend in ["#", "="]:
                return trend.items[0]
            else:
                return trend

        if self.trend in ["+", "-"] and len(self.items) > 0:
            return firstest(self.items[0])
        elif self.trend in ["#", "="]:
            return self.items[0]
        else:
            return None

    def lastTrend(self) -> Trend:
        """
        获取最后一个非单体趋势
        :return:
        """
        if not self.isTrend():
            return None
        elif len(self.items) > 0:
            lastT = self.items[-1].lastTrend()
            if lastT is not None:
                return lastT
            else:
                return self
        else:
            return self

    def isRise(self):
        return self.trend in ["+", "++", "#"]

    def isFell(self):
        return self.trend in ["-", "--", "="]

    def trendFirstItem(self) -> Trend:
        if self.isTrend():
            return self.items[-1].lastItem()
        return None

    def isConsolidation(self):
        """
        是否是盘整
        :return:
        """
        return self.trend in ["=", "#"]

    def isTrend(self):
        """
        是否是趋势 也就是 + 或 -
        :return:
        """
        return self.trend in ["-", "+"] and len(self.items) > 0

    def updateBoundary(self):
        if len(self.items) > 0:
            for item in self.items:
                item.updateBoundary()
            else:
                self.boundary.start = self.items[0].boundary.start
                self.boundary.end = self.items[-1].boundary.end

    def computeDeviate(self) -> float:
        """
        计算背驰量
        :return:
        """
        if len(self.items) == 0:
            return 0
        if self.isConsolidation():
            return 0
        else:
            lastT = self.lastTrend()
            if lastT is None:
                return 0
            return lastT.items[-1].area/lastT.firstItem().area

    def markParent(self):
        if len(self.items) > 0:
            for item in self.items:
                item.parent = self
                item.markParent()

    def parentTrendSequeue(self) -> List[str]:
        trends = [self.trend]
        if self.parent is not None:
            trends = trends + self.parent.parentTrendSequeue()
        return trends

    def getTrendParent(self):
        if self.isTrend():
            return self
        elif self.parent is not None:
            return self.parent.getTrendParent()
        else:
            return None

    def analysis(self, pre: Trend) -> TrendStatus:
        self.markParent()
        if len(self.items) == 0 and pre is not None:
            pre.markParent()
            trendSequeue = pre.lastItem().parent.parentTrendSequeue() if pre.lastItem().parent is not None else None
            if pre.isConsolidation():
                preFirst = pre.firstItem()
                # 盘整延续
                if (preFirst.amplitude.kh >= self.amplitude.kh or preFirst.amplitude.kh >= self.amplitude.mh) \
                    and (preFirst.amplitude.kl <= self.amplitude.kl or preFirst.amplitude.kl <= self.amplitude.ml):
                    return TrendStatus(self.trend, None, 0, TrendStatus(pre.trend, trendSequeue, 0))
                # 突破盘整并转势
                elif (preFirst.amplitude.kh < self.amplitude.kh or preFirst.amplitude.kh < self.amplitude.mh) \
                    and (preFirst.amplitude.kl > self.amplitude.kl or preFirst.amplitude.kl > self.amplitude.ml):
                    return TrendStatus(self.trend, None, 0, breaked=True, pre=TrendStatus(pre.trend, trendSequeue, 0))
            elif pre.trend in ["+", "++", "-", "--"]:
                trendS = TrendStatus(self.trend, None, 0)
                lastT = pre.lastItem().getTrendParent()
                trendS.pre = TrendStatus(pre.trend, trendSequeue, 0)
                if lastT is not None:
                    trendS.pre.value = lastT.computeDeviate()
                    if len(lastT.items) > 0 and ((pre.isRise() and (self.amplitude.kl < lastT.items[-1].amplitude.kl or self.amplitude.ml < lastT.items[-1].amplitude.kl)) \
                        or (pre.isFell() and (self.amplitude.kh > lastT.items[-1].amplitude.kh or self.amplitude.mh > lastT.items[-1].amplitude.kh))):
                        trendS.breaked = True
                return trendS
        # 多个子项
        elif len(self.items) > 0:
            lastI = self.lastItem()
            trendP = lastI.parent.getTrendParent() if lastI.parent is not None else None
            return TrendStatus(lastI.trend,
                               lastI.parent.parentTrendSequeue() if lastI.parent is not None else None,
                               trendP.computeDeviate() if trendP is not None and trendP.isTrend() else 0)
        else:
            return TrendStatus(self.trend, None, 0)


    def __str__(self):
        return "trend: " + self.trend + "; boundary: " + str(self.boundary) + "; amplitude:" + str(self.amplitude) + "; area:" + str(self.area) + "; items:" + str(len(self.items)) + "个"


class TrendAreaAnalysis:
    def __init__(self):
        self.records: Dict[str, Dict[str, List[float]]] = {
            "+": {
                "+": [],
                "-": []
            },
            "-": {
                "-": [],
                "+": []
            },
            "#": {
                "h": [],
                "+": [],
                "-": []
            },
            "=": {
                "h": [],
                "+": [],
                "-": []
            }
        }

    def addTrend(self, trendType: str, item: Trend, isConsolidationHead: bool = False):
        sub = item.trend
        if isConsolidationHead:
            sub = "h"
        self.records[trendType][sub].append(item.area)

    def isConsolidation(self, trendType: str, head: Trend, mid: Trend, cur: Trend) -> bool:
        assert trendType in ["=", "#"]
        records = self.records[trendType][mid.trend]

        if len(records) >= 20 and head.area > (statistics.median(self.records[trendType]["h"])) * 2 and mid.area > (statistics.median(records)) * 4:
            return False
        else:
            return True


class ChanTactic(Tactic):
    classInfo = "缠背驰策略类"

    def __init__(self):
        super(ChanTactic, self).__init__()
        self.period: Period = Period.day
        """
        分析周期
        """
        self.selectionOption: ChanTacticSelectionOption = None
        """
        策略选择选项
        """
        self.notes = {}
    
    def compute(self):
        assert self.selectionOption is not None
        self.setPeriod(self.period)
        clo = close()
        ma5 = sma(clo, 5)
        ma10 = sma(clo, 10)
        cro = cross(ma5, ma10)
        boundaries = self.identifyCrossRange(cro)
        trends = self.identifyTrend(ma5, cro, high(), low(), boundaries)
        self.analysis(trends)

    def analysis(self, trends: List[Trend]):
        trendStatus = trends[-1].analysis(trends[-2] if len(trends) >= 2 else None)
        if trendStatus is None:
            return
        trend = ""
        deviateDegree = 0
        deviateStatus = 0
        if trendStatus.isTrendItem() and trendStatus.isDeviate():
            deviateStatus = 1
            deviateDegree = 1 - trendStatus.value
            trend = trendStatus.trend
        elif not trendStatus.isTrend() and trendStatus.pre is not None and trendStatus.pre.isTrendItem():
            deviateStatus = 2
            deviateDegree = 1 - trendStatus.pre.value
            trend = trendStatus.pre.trend
        if self.selectionOption.compute(trend, deviateStatus, deviateDegree=deviateDegree):
            logger.error(f"{self.selectionOption.sucessEventType.name}-{self.context.security}-{self.selectionOption.nextPeriod.name}")
            self.emit(self.selectionOption.sucessEventType, (self.context.security, self.selectionOption.nextPeriod))
        else:
            logger.error(
                f"{self.selectionOption.failEventType.name}-{self.context.security}-{self.selectionOption.nextPeriod.name}")
            self.emit(self.selectionOption.failEventType, (self.context.security, self.selectionOption.nextPeriod))

    def identifyCrossRange(self, data: Series) -> List[Tuple[str, str]]:
        data = data.dropna()
        data = data.where(data > 0, -1).where(data < 0, 1).astype("str").replace("-1.0", "-").replace("1.0", "+")
        # df = data.reindex(data.index[::-1]) # 反向
        df = data
        v = "".join(df.values.tolist())
        result = re.finditer(r'(\++|-+)', v)
        boundaries = []
        for r in result:
            boundaries.append(('+' if '+' in df[df.index[r.start()]] else '-', df.index[r.start()].strftime(DateFormat.Y_m_d_H_M_S.value), df.index[r.end()-1].strftime(DateFormat.Y_m_d_H_M_S.value)))
        return boundaries

    def identifyTrend(self, data: Series, cross: Series, high: Series, low: Series, boundaries: List[Tuple[str, str]]) -> List[Trend]:
        """
        识别趋势
        :param data:
        :param boundaries: ()
        :return:
        """
        result: List[Trend] = []
        temp: Trend = None
        cur: Trend = None
        areaAnalysis = TrendAreaAnalysis()

        def identifyTrendType(trends: List[Trend]):
            t2 = trends[-1]
            if t2.trend in ["+", "++"]:
                if t2.amplitude.mh < cur.amplitude.kh \
                        and t2.amplitude.kl < temp.amplitude.ml \
                        and temp.amplitude.mh < cur.amplitude.kh:
                    if temp.count <= 3:
                        return "++"
                    return "+"
                elif ((t2.lastItem().amplitude.kh >= cur.amplitude.kh) or (t2.lastItem().amplitude.kh >= cur.amplitude.mh)) \
                        and ((t2.lastItem().amplitude.kh >= temp.amplitude.kh) or (t2.lastItem().amplitude.kh >= temp.amplitude.mh)) \
                        and ((t2.lastItem().amplitude.kl <= cur.amplitude.kl) or (t2.lastItem().amplitude.kl >= cur.amplitude.ml)) \
                        and ((t2.lastItem().amplitude.kl <= temp.amplitude.kl) or (t2.lastItem().amplitude.kl <= temp.amplitude.ml)):
                    # if areaAnalysis.isConsolidation("#", t2.lastItem(), temp, cur):
                    #     return "#"
                    # else:
                    #     return None
                    return "#"
                else:
                    return None
            elif t2.trend == "#":
                if t2.amplitude.mh < cur.amplitude.kh \
                        and t2.amplitude.kl < temp.amplitude.ml \
                        and temp.amplitude.mh < cur.amplitude.kh:
                    return "+"
                elif ((t2.firstItem().amplitude.kh >= cur.amplitude.kh) or (t2.firstItem().amplitude.kh >= cur.amplitude.mh)) \
                        and ((t2.firstItem().amplitude.kh >= temp.amplitude.kh) or (t2.firstItem().amplitude.kh >= temp.amplitude.mh)) \
                        and ((t2.firstItem().amplitude.kl <= cur.amplitude.kl) or (t2.firstItem().amplitude.kl >= cur.amplitude.ml)) \
                        and ((t2.firstItem().amplitude.kl <= temp.amplitude.kl) or (t2.firstItem().amplitude.kl <= temp.amplitude.ml)):
                    return "#"
                else:
                    return None
            elif t2.trend in ["-", "--"]:
                if ((t2.lastItem().amplitude.kl <= cur.amplitude.kl) or (t2.lastItem().amplitude.kl <= cur.amplitude.ml)) \
                        and ((temp.amplitude.kh < t2.lastItem().amplitude.kh) or (temp.amplitude.mh < t2.lastItem().amplitude.mh)):
                    # if areaAnalysis.isConsolidation("=", t2.lastItem(), temp, cur):
                    #     return "="
                    # else:
                    #     return None
                    return "="
                elif t2.amplitude.ml > cur.amplitude.kl \
                        and t2.amplitude.kh > temp.amplitude.mh \
                        and temp.amplitude.ml > cur.amplitude.kl:
                    if temp.count <= 3:
                        return "--"
                    return "-"
                else:
                    return None
            elif t2.trend == "=":
                if ((t2.firstItem().amplitude.kl <= cur.amplitude.kl) or (t2.firstItem().amplitude.kl <= cur.amplitude.ml)) \
                        and ((temp.amplitude.kh < t2.firstItem().amplitude.kh) or (temp.amplitude.mh < t2.firstItem().amplitude.mh)):
                    return "="
                elif t2.amplitude.ml > cur.amplitude.kl \
                        and t2.amplitude.kh > temp.amplitude.mh \
                        and temp.amplitude.ml > cur.amplitude.kl:
                    return "-"
                else:
                    return None

        def parseFromDeepest(trends: List[Trend]):
            nonlocal temp
            nonlocal cur
            nonlocal result
            if trends[-1].isTrend():
                return parseFromDeepest(trends[-1].items)
            trendType = identifyTrendType(trends)
            if trendType is None:
                return False
            if trendType in ["-", "+"]:
                t2 = trends.pop()
                # areaAnalysis.addTrend(trendType, temp)
                # areaAnalysis.addTrend(trendType, cur)
                # if len(t2.items) == 0:
                #     areaAnalysis.addTrend(trendType, t2)
                t = Trend()
                t.trend = trendType
                t.boundary = Boundary(t2.boundary.start, cur.boundary.end)
                t.count = t2.count + temp.count + cur.count
                t.area = t2.area + cur.area
                t.items = [t2, temp, cur]
                t.amplitude = Amplitude(max(t2.amplitude.kh, cur.amplitude.kh), min(t2.amplitude.kl, cur.amplitude.kl), max(t2.amplitude.mh, cur.amplitude.mh), min(t2.amplitude.ml, cur.amplitude.ml))
                temp = None
                cur = None
                trends.append(t)
                return True
            elif trendType in ["=", "#"]:
                t2 = trends.pop()
                items = []
                if t2.isConsolidation():
                    items.extend(t2.items)
                else:
                    items.append(t2)
                items.append(temp)
                items.append(cur)
                t = Trend()
                t.trend = "#" if t2.isRise() else "="
                t.boundary = Boundary(t2.boundary.start, cur.boundary.end)
                t.count = t2.count + temp.count + cur.count
                t.area = t2.area
                t.items = items
                t.amplitude = t2.amplitude
                temp = None
                cur = None
                trends.append(t)
                return True
            elif trendType in ["--", "++"]:
                t = trends[-1].lastItem()
                t2 = copy.deepcopy(t)
                t.trend = trendType
                t.boundary = Boundary(t2.boundary.start, cur.boundary.end)
                t.count = t2.count + temp.count + cur.count
                t.area = t2.area + cur.area
                t.items = [t2, temp, cur]
                t.amplitude = Amplitude(max(t2.amplitude.kh, cur.amplitude.kh), min(t2.amplitude.kl, cur.amplitude.kl), max(t2.amplitude.mh, cur.amplitude.mh), min(t2.amplitude.ml, cur.amplitude.ml))
                temp = None
                cur = None
                return True
            else:
                return False


        def reduce(trends: List[Trend]):
            nonlocal temp
            nonlocal cur
            # nonlocal  result
            # if self.context.security in ['601390.SH']:
            #     if cur.boundary.start.startswith("2023-05-11") or temp.boundary.start.startswith("2023-05-11"):
            #         print()
            if not parseFromDeepest(trends):
                trends.append(temp)
                temp = cur
                cur = None
            else:
                trends[-1].updateBoundary()

        # 合并两条均线围成的面积
        for boundary in boundaries:
            d = data.loc[boundary[1]: boundary[2]]
            h = high.loc[boundary[1]: boundary[2]]
            l = low.loc[boundary[1]: boundary[2]]
            trend = Trend()
            trend.trend = boundary[0]
            trend.boundary = Boundary(boundary[1], boundary[2])
            trend.count = d.count()
            trend.area = abs(cross.loc[boundary[1]: boundary[2]].sum())
            trend.items = []
            trend.amplitude = Amplitude(h.max(), l.min(), d.max(), d.min())
            if len(result) == 0:
                result.append(trend)
                trend = None
                continue
            elif temp is None:
                temp = trend
                trend = None
                continue
            elif cur is None:
                cur = trend
                trend = None
            reduce(result)
        if temp is not None:
            result.append(temp)
        if cur is not None:
            result.append(cur)
        return result

    def isDeviate(self, data1: Series, data2: Series):
        """
        是否背驰
        :return:
        """
        return abs(data1.sum()) > abs(data2.sum())


if __name__ == "__main__":
    from core import threadLocal
    from core.context import Context
    from core.enums import Period
    threadLocal.add("threadName", "test")
    con: Context = Context.getInstance(name="test")
    con.set_Security("002044.SZ", period=Period.day)
    chan = ChanTactic()
    chan.compute()
    # from domain.transaction_data.spider.xueqiu_spider import kline
    # result = kline("SZ002044", period=Period.minute30, days=10000)
    # print(result)